﻿<!DOCTYPE html>
<html>
<head>
  <title>Tree Layout</title>
  <!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
  <meta name="description" content="Interactive demonstration of tree layout features by the TreeLayout class." />
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <script src="../release/go.js"></script>
  <script src="../assets/js/goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
  <style type="text/css">
    input[type="number"] {
      width: 45px;
    }
  </style>
  <script id="code">
    function init() {
      if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      myDiagram =
        $(go.Diagram, "myDiagramDiv",  // must be the ID or reference to div
          {
            initialAutoScale: go.Diagram.UniformToFill,
            layout: $(go.TreeLayout,
              { comparer: go.LayoutVertex.smartComparer }) // have the comparer sort by numbers as well as letters
            // other properties are set by the layout function, defined below
          });

      // define the Node template
      myDiagram.nodeTemplate =
        $(go.Node, "Spot",
          { locationSpot: go.Spot.Center },
          new go.Binding("text", "text"),  // for sorting
          $(go.Shape, "Ellipse",
            {
              fill: "lightgray",  // the initial value, but data binding may provide different value
              stroke: null,
              desiredSize: new go.Size(30, 30)
            },
            new go.Binding("desiredSize", "size"),
            new go.Binding("fill", "fill")),
          $(go.TextBlock,
            new go.Binding("text", "text"))
        );

      // define the Link template
      myDiagram.linkTemplate =
        $(go.Link,
          {
            routing: go.Link.Orthogonal,
            selectable: false
          },
          $(go.Shape,
            { strokeWidth: 3, stroke: "#333" }));

      // generate a tree with the default values
      rebuildGraph();
    }

    function rebuildGraph() {
      var minNodes = document.getElementById("minNodes").value;
      minNodes = parseInt(minNodes, 10);

      var maxNodes = document.getElementById("maxNodes").value;
      maxNodes = parseInt(maxNodes, 10);

      var minChil = document.getElementById("minChil").value;
      minChil = parseInt(minChil, 10);

      var maxChil = document.getElementById("maxChil").value;
      maxChil = parseInt(maxChil, 10);

      var hasRandomSizes = document.getElementById("randomSizes").checked;

      // create and assign a new model
      var nodeDataArray = generateNodeData(minNodes, maxNodes, minChil, maxChil, hasRandomSizes);
      myDiagram.model = new go.TreeModel(nodeDataArray);

      // update the diagram layout customized by the various control values
      layout();
    }

    // Creates a random number (between MIN and MAX) of randomly colored nodes.
    function generateNodeData(minNodes, maxNodes, minChil, maxChil, hasRandomSizes) {
      var nodeArray = [];
      if (minNodes === undefined || isNaN(minNodes) || minNodes < 1) minNodes = 1;
      if (maxNodes === undefined || isNaN(maxNodes) || maxNodes < minNodes) maxNodes = minNodes;

      // Create a bunch of node data
      var numNodes = Math.floor(Math.random() * (maxNodes - minNodes + 1)) + minNodes;
      for (var i = 0; i < numNodes; i++) {
        nodeArray.push({
          key: i,  // the unique identifier
          // "parent" is set by code below that assigns children
          text: i.toString(),  // some text to be shown by the node template
          fill: go.Brush.randomColor(),  // a color to be shown by the node template
          size: (hasRandomSizes) ? new go.Size(Math.random() * 50 + 20, Math.random() * 50 + 20) : new go.Size(30, 30)
        });
      }

      // Randomize the node data
      for (i = 0; i < nodeArray.length; i++) {
        var swap = Math.floor(Math.random() * nodeArray.length);
        var temp = nodeArray[swap];
        nodeArray[swap] = nodeArray[i];
        nodeArray[i] = temp;
      }

      // Takes the random collection of node data and creates a random tree with them.
      // Respects the minimum and maximum number of links from each node.
      // The minimum can be disregarded if we run out of nodes to link to.
      if (nodeArray.length > 1) {
        if (minChil === undefined || isNaN(minChil) || minChil < 0) minChil = 0;
        if (maxChil === undefined || isNaN(maxChil) || maxChil < minChil) maxChil = minChil;

        // keep the Set of node data that do not yet have a parent
        var available = new go.Set();
        available.addAll(nodeArray);
        for (var i = 0; i < nodeArray.length; i++) {
          var parent = nodeArray[i];
          available.remove(parent);

          // assign some number of node data as children of this parent node data
          var children = Math.floor(Math.random() * (maxChil - minChil + 1)) + minChil;
          for (var j = 0; j < children; j++) {
            var child = available.first();
            if (child === null) break;  // oops, ran out already
            available.remove(child);
            // have the child node data refer to the parent node data by its key
            child.parent = parent.key;
          }
          if (available.count === 0) break;  // nothing left?
        }
      }
      return nodeArray;
    }

    // Update the layout from the controls, and then perform the layout again
    function layout() {
      myDiagram.startTransaction("change Layout");
      var lay = myDiagram.layout;

      var style = document.getElementById("style").value;
      if (style === "Layered") lay.treeStyle = go.TreeLayout.StyleLayered;
      else if (style === "Alternating") lay.treeStyle = go.TreeLayout.StyleAlternating;
      else if (style === "LastParents") lay.treeStyle = go.TreeLayout.StyleLastParents;
      else if (style === "RootOnly") lay.treeStyle = go.TreeLayout.StyleRootOnly;

      var layerStyle = document.getElementById("layerStyle").value;
      if (layerStyle === "Individual") lay.layerStyle = go.TreeLayout.LayerIndividual;
      else if (layerStyle === "Siblings") lay.layerStyle = go.TreeLayout.LayerSiblings;
      else if (layerStyle === "Uniform") lay.layerStyle = go.TreeLayout.LayerUniform;

      var angle = getRadioValue("angle");
      angle = parseFloat(angle, 10);
      lay.angle = angle;

      var align = document.getElementById("align").value;
      if (align === "CenterChildren") lay.alignment = go.TreeLayout.AlignmentCenterChildren;
      else if (align === "CenterSubtrees") lay.alignment = go.TreeLayout.AlignmentCenterSubtrees;
      else if (align === "Start") lay.alignment = go.TreeLayout.AlignmentStart;
      else if (align === "End") lay.alignment = go.TreeLayout.AlignmentEnd;
      else if (align === "Bus") lay.alignment = go.TreeLayout.AlignmentBus;
      else if (align === "BusBranching") lay.alignment = go.TreeLayout.AlignmentBusBranching;
      else if (align === "TopLeftBus") lay.alignment = go.TreeLayout.AlignmentTopLeftBus;
      else if (align === "BottomRightBus") lay.alignment = go.TreeLayout.AlignmentBottomRightBus;

      var nodeSpacing = document.getElementById("nodeSpacing").value;
      nodeSpacing = parseFloat(nodeSpacing, 10);
      lay.nodeSpacing = nodeSpacing;

      var nodeIndent = document.getElementById("nodeIndent").value;
      nodeIndent = parseFloat(nodeIndent, 10);
      lay.nodeIndent = nodeIndent;

      var nodeIndentPastParent = document.getElementById("nodeIndentPastParent").value;
      nodeIndentPastParent = parseFloat(nodeIndentPastParent, 10);
      lay.nodeIndentPastParent = nodeIndentPastParent;

      var layerSpacing = document.getElementById("layerSpacing").value;
      layerSpacing = parseFloat(layerSpacing, 10);
      lay.layerSpacing = layerSpacing;

      var layerSpacingParentOverlap = document.getElementById("layerSpacingParentOverlap").value;
      layerSpacingParentOverlap = parseFloat(layerSpacingParentOverlap, 10);
      lay.layerSpacingParentOverlap = layerSpacingParentOverlap;

      var sorting = document.getElementById("sorting").value;
      if (sorting === "Forwards") lay.sorting = go.TreeLayout.SortingForwards;
      else if (sorting === "Reverse") lay.sorting = go.TreeLayout.SortingReverse;
      else if (sorting === "Ascending") lay.sorting = go.TreeLayout.SortingAscending;
      else if (sorting === "Descending") lay.sorting = go.TreeLayout.SortingDescending;

      var compaction = getRadioValue("compaction");
      if (compaction === "Block") lay.compaction = go.TreeLayout.CompactionBlock;
      else if (compaction === "None") lay.compaction = go.TreeLayout.CompactionNone;

      var breadthLimit = document.getElementById("breadthLimit").value;
      breadthLimit = parseFloat(breadthLimit, 10);
      lay.breadthLimit = breadthLimit;

      var rowSpacing = document.getElementById("rowSpacing").value;
      rowSpacing = parseFloat(rowSpacing, 10);
      lay.rowSpacing = rowSpacing;

      var rowIndent = document.getElementById("rowIndent").value;
      rowIndent = parseFloat(rowIndent, 10);
      lay.rowIndent = rowIndent;

      var setsPortSpot = document.getElementById("setsPortSpot").checked;
      lay.setsPortSpot = setsPortSpot;

      var setsChildPortSpot = document.getElementById("setsChildPortSpot").checked;
      lay.setsChildPortSpot = setsChildPortSpot;

      if (style !== "Layered") {
        var altAngle = getRadioValue("altAngle");
        altAngle = parseFloat(altAngle, 10);
        lay.alternateAngle = altAngle;

        var altAlign = document.getElementById("altAlign").value;
        if (altAlign === "CenterChildren") lay.alternateAlignment = go.TreeLayout.AlignmentCenterChildren;
        else if (altAlign === "CenterSubtrees") lay.alternateAlignment = go.TreeLayout.AlignmentCenterSubtrees;
        else if (altAlign === "Start") lay.alternateAlignment = go.TreeLayout.AlignmentStart;
        else if (altAlign === "End") lay.alternateAlignment = go.TreeLayout.AlignmentEnd;
        else if (altAlign === "Bus") lay.alternateAlignment = go.TreeLayout.AlignmentBus;
        else if (altAlign === "BusBranching") lay.alternateAlignment = go.TreeLayout.AlignmentBusBranching;
        else if (altAlign === "TopLeftBus") lay.alternateAlignment = go.TreeLayout.AlignmentTopLeftBus;
        else if (altAlign === "BottomRightBus") lay.alternateAlignment = go.TreeLayout.AlignmentBottomRightBus;

        var altNodeSpacing = document.getElementById("altNodeSpacing").value;
        altNodeSpacing = parseFloat(altNodeSpacing, 10);
        lay.alternateNodeSpacing = altNodeSpacing;

        var altNodeIndent = document.getElementById("altNodeIndent").value;
        altNodeIndent = parseFloat(altNodeIndent, 10);
        lay.alternateNodeIndent = altNodeIndent;

        var altNodeIndentPastParent = document.getElementById("altNodeIndentPastParent").value;
        altNodeIndentPastParent = parseFloat(altNodeIndentPastParent, 10);
        lay.alternateNodeIndentPastParent = altNodeIndentPastParent;

        var altLayerSpacing = document.getElementById("altLayerSpacing").value;
        altLayerSpacing = parseFloat(altLayerSpacing, 10);
        lay.alternateLayerSpacing = altLayerSpacing;

        var altLayerSpacingParentOverlap = document.getElementById("altLayerSpacingParentOverlap").value;
        altLayerSpacingParentOverlap = parseFloat(altLayerSpacingParentOverlap, 10);
        lay.alternateLayerSpacingParentOverlap = altLayerSpacingParentOverlap;

        var altSorting = document.getElementById("altSorting").value;
        if (altSorting === "Forwards") lay.alternateSorting = go.TreeLayout.SortingForwards;
        else if (altSorting === "Reverse") lay.alternateSorting = go.TreeLayout.SortingReverse;
        else if (altSorting === "Ascending") lay.alternateSorting = go.TreeLayout.SortingAscending;
        else if (altSorting === "Descending") lay.alternateSorting = go.TreeLayout.SortingDescending;

        var altCompaction = getRadioValue("altCompaction");
        if (altCompaction === "Block") lay.alternateCompaction = go.TreeLayout.CompactionBlock;
        else if (altCompaction === "None") lay.alternateCompaction = go.TreeLayout.CompactionNone;

        var altBreadthLimit = document.getElementById("altBreadthLimit").value;
        altBreadthLimit = parseFloat(altBreadthLimit, 10);
        lay.alternateBreadthLimit = altBreadthLimit;

        var altRowSpacing = document.getElementById("altRowSpacing").value;
        altRowSpacing = parseFloat(altRowSpacing, 10);
        lay.alternateRowSpacing = altRowSpacing;

        var altRowIndent = document.getElementById("altRowIndent").value;
        altRowIndent = parseFloat(altRowIndent, 10);
        lay.alternateRowIndent = altRowIndent;

        var altSetsPortSpot = document.getElementById("altSetsPortSpot").checked;
        lay.alternateSetsPortSpot = altSetsPortSpot;

        var altSetsChildPortSpot = document.getElementById("altSetsChildPortSpot").checked;
        lay.alternateSetsChildPortSpot = altSetsChildPortSpot;
      }

      myDiagram.commitTransaction("change Layout");
    }

    function getRadioValue(name) {
      var radio = document.getElementsByName(name);
      for (var i = 0; i < radio.length; i++)
        if (radio[i].checked) return radio[i].value;
    }
  </script>
</head>
<body onload="init()">
<div id="sample">
  <div style="margin-bottom: 5px; padding: 5px; background-color: aliceblue">
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>Tree Style</b><br />
      <select name="style" id="style" onchange="layout()">
        <option value="Layered" selected="selected">Layered</option>
        <option value="Alternating">Alternating</option>
        <option value="LastParents">LastParents</option>
        <option value="RootOnly">RootOnly</option>
      </select>
      <br />
      <b>Layer Style</b><br />
      <select name="layerStyle" id="layerStyle" onchange="layout()">
        <option value="Individual" selected="selected">Individual</option>
        <option value="Siblings">Siblings</option>
        <option value="Uniform">Uniform</option>
      </select>
      <br />
      <br />
      <b>New Tree</b><br />
      MinNodes: <input type="number" width="2" id="minNodes" value="20" /><br />
      MaxNodes: <input type="number" width="2" id="maxNodes" value="100" /><br />
      MinChildren: <input type="number" width="2" id="minChil" value="1" /><br />
      MaxChildren: <input type="number" width="2" id="maxChil" value="3" /><br />
      Random Sizes: <input type="checkbox" id="randomSizes" /><br />
      <button type="button" onclick="rebuildGraph()">Generate Tree</button>
    </span>
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>Default Properties</b><br />
      Angle:
        <input type="radio" name="angle" onclick="layout()" value="0" checked="checked" />Right
        <input type="radio" name="angle" onclick="layout()" value="90" />Down
        <input type="radio" name="angle" onclick="layout()" value="180" />Left
        <input type="radio" name="angle" onclick="layout()" value="270" />Up<br />
      Alignment:
        <select name="align" id="align" onchange="layout()">
          <option value="CenterChildren" selected="selected">CenterChildren</option>
          <option value="CenterSubtrees">CenterSubtrees</option>
          <option value="Start">Start</option>
          <option value="End">End</option>
          <option value="Bus">Bus</option>
          <option value="BusBranching">BusBranching</option>
          <option value="TopLeftBus">TopLeftBus</option>
          <option value="BottomRightBus">BottomRightBus</option>
        </select>
        <br />
      NodeSpacing: <input type="number" size="2" id="nodeSpacing" value="20" onchange="layout()" /> (negative causes overlaps)<br />
      NodeIndent: <input type="number" size="2" id="nodeIndent" value="0" onchange="layout()" /> (when Start or End; >= 0)<br />
      NodeIndentPastParent: <input type="number" size="2" id="nodeIndentPastParent" value="0" onchange="layout()" /> (fraction; 0-1)<br />
      LayerSpacing: <input type="number" size="2" id="layerSpacing" value="50" onchange="layout()" /> (negative causes overlaps)<br />
      LayerSpacingParentOverlap: <input type="number" size="2" id="layerSpacingParentOverlap" value="0" onchange="layout()" /> (fraction; 0-1)<br />
      Sorting:
        <select name="sorting" id="sorting" onchange="layout()">
          <option value="Forwards" selected="selected">Forwards</option>
          <option value="Reverse">Reverse</option>
          <option value="Ascending">Ascending</option>
          <option value="Descending">Descending</option>
        </select>
        <br />
      Compaction:
        <input type="radio" name="compaction" onclick="layout()" value="Block" checked="checked" /> Block
        <input type="radio" name="compaction" onclick="layout()" value="None" /> None<br />
      BreadthLimit: <input type="number" size="2" id="breadthLimit" value="0" onchange="layout()" /> (0 means no limit)<br />
      RowSpacing: <input type="number" size="2" id="rowSpacing" value="25" onchange="layout()" /> (negative causes overlaps)<br />
      RowIndent: <input type="number" size="2" id="rowIndent" value="10" onchange="layout()" /> (>= 0)<br />
      SetsPortSpot: <input type="checkbox" id="setsPortSpot" checked="checked" onclick="layout()" />
      SetsChildPortSpot: <input type="checkbox" id="setsChildPortSpot" checked="checked" onclick="layout()" /><br />
    </span>
    <span style="display: inline-block; vertical-align: top; padding: 5px">
      <b>Alternates</b>
      (only when TreeStyle is not Layered)
      <br />
      Angle:
        <input type="radio" name="altAngle" onclick="layout()" value="0" checked="checked" />Right
        <input type="radio" name="altAngle" onclick="layout()" value="90" />Down
        <input type="radio" name="altAngle" onclick="layout()" value="180" />Left
        <input type="radio" name="altAngle" onclick="layout()" value="270" />Up<br />
      Alignment:
        <select name="altAlign" id="altAlign" onchange="layout()">
          <option value="CenterChildren" selected="selected">CenterChildren</option>
          <option value="CenterSubtrees">CenterSubtrees</option>
          <option value="Start">Start</option>
          <option value="End">End</option>
          <option value="Bus">Bus</option>
          <option value="BusBranching">BusBranching</option>
          <option value="TopLeftBus">TopLeftBus</option>
          <option value="BottomRightBus">BottomRightBus</option>
        </select>
        <br />
      NodeSpacing: <input type="number" size="2" id="altNodeSpacing" value="20" onchange="layout()" /> (negative causes overlaps)<br />
      NodeIndent: <input type="number" size="2" id="altNodeIndent" value="0" onchange="layout()" /> (when Start or End; >= 0)<br />
      NodeIndentPastParent: <input type="number" size="2" id="altNodeIndentPastParent" value="0" onchange="layout()" /> (fraction; 0-1)<br />
      LayerSpacing: <input type="number" size="2" id="altLayerSpacing" value="50" onchange="layout()" /> (negative causes overlaps)<br />
      LayerSpacingParentOverlap: <input type="number" size="2" id="altLayerSpacingParentOverlap" value="0" onchange="layout()" /> (fraction; 0-1)<br />
      Sorting:
        <select name="altSorting" id="altSorting" onchange="layout()">
          <option value="Forwards" selected="selected">Forwards</option>
          <option value="Reverse">Reverse</option>
          <option value="Ascending">Ascending</option>
          <option value="Descending">Descending</option>
        </select>
        <br />
      Compaction:
        <input type="radio" name="altCompaction" onclick="layout()" value="Block" checked="checked" /> Block
        <input type="radio" name="altCompaction" onclick="layout()" value="None" /> None<br />
      BreadthLimit: <input type="number" size="2" id="altBreadthLimit" value="0" onchange="layout()" /> (0 means no limit)<br />
      RowSpacing: <input type="number" size="2" id="altRowSpacing" value="25" onchange="layout()" /> (negative causes overlaps)<br />
      RowIndent: <input type="number" size="2" id="altRowIndent" value="10" onchange="layout()" /> (>= 0)<br />
      SetsPortSpot: <input type="checkbox" id="altSetsPortSpot" checked="checked" onclick="layout()" />
      SetsChildPortSpot: <input type="checkbox" id="altSetsChildPortSpot" checked="checked" onclick="layout()" /><br />
    </span>
  </div>
  <div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
  <p>
    For information on <b>TreeLayout</b> and its properties, see the <a>TreeLayout</a> documentation page.
  </p>
</div>
</body>
</html>